// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/macros.h"
#include "base/message_loop/message_loop.h"
#include "net/base/network_change_notifier.h"
#include "net/base/network_change_notifier_factory.h"
#include "net/base/network_change_notifier_win.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

using ::testing::AtLeast;
using ::testing::Invoke;
using ::testing::Return;
using ::testing::StrictMock;

namespace net {

namespace {

    // Subclass of NetworkChangeNotifierWin that overrides functions so that no
    // Windows API networking functions are ever called.
    class TestNetworkChangeNotifierWin : public NetworkChangeNotifierWin {
    public:
        TestNetworkChangeNotifierWin() { }

        ~TestNetworkChangeNotifierWin() override
        {
            // This is needed so we don't try to stop watching for IP address changes,
            // as we never actually started.
            set_is_watching(false);
        }

        // From NetworkChangeNotifierWin.
        NetworkChangeNotifier::ConnectionType RecomputeCurrentConnectionType()
            const override
        {
            return NetworkChangeNotifier::CONNECTION_UNKNOWN;
        }

        // From NetworkChangeNotifierWin.
        MOCK_METHOD0(WatchForAddressChangeInternal, bool());

    private:
        DISALLOW_COPY_AND_ASSIGN(TestNetworkChangeNotifierWin);
    };

    class TestIPAddressObserver : public NetworkChangeNotifier::IPAddressObserver {
    public:
        TestIPAddressObserver()
        {
            NetworkChangeNotifier::AddIPAddressObserver(this);
        }

        ~TestIPAddressObserver()
        {
            NetworkChangeNotifier::RemoveIPAddressObserver(this);
        }

        MOCK_METHOD0(OnIPAddressChanged, void());

    private:
        DISALLOW_COPY_AND_ASSIGN(TestIPAddressObserver);
    };

    bool ExitMessageLoopAndReturnFalse()
    {
        base::MessageLoop::current()->QuitWhenIdle();
        return false;
    }

} // namespace

class NetworkChangeNotifierWinTest : public testing::Test {
public:
    // Calls WatchForAddressChange, and simulates a WatchForAddressChangeInternal
    // success.  Expects that |network_change_notifier_| has just been created, so
    // it's not watching anything yet, and there have been no previous
    // WatchForAddressChangeInternal failures.
    void StartWatchingAndSucceed()
    {
        EXPECT_FALSE(network_change_notifier_.is_watching());
        EXPECT_EQ(0, network_change_notifier_.sequential_failures());

        EXPECT_CALL(test_ip_address_observer_, OnIPAddressChanged()).Times(0);
        EXPECT_CALL(network_change_notifier_, WatchForAddressChangeInternal())
            .Times(1)
            .WillOnce(Return(true));

        network_change_notifier_.WatchForAddressChange();

        EXPECT_TRUE(network_change_notifier_.is_watching());
        EXPECT_EQ(0, network_change_notifier_.sequential_failures());

        // If a task to notify observers of the IP address change event was
        // incorrectly posted, make sure it gets run to trigger a failure.
        base::MessageLoop::current()->RunUntilIdle();
    }

    // Calls WatchForAddressChange, and simulates a WatchForAddressChangeInternal
    // failure.
    void StartWatchingAndFail()
    {
        EXPECT_FALSE(network_change_notifier_.is_watching());
        EXPECT_EQ(0, network_change_notifier_.sequential_failures());

        EXPECT_CALL(test_ip_address_observer_, OnIPAddressChanged()).Times(0);
        EXPECT_CALL(network_change_notifier_, WatchForAddressChangeInternal())
            // Due to an expected race, it's theoretically possible for more than
            // one call to occur, though unlikely.
            .Times(AtLeast(1))
            .WillRepeatedly(Return(false));

        network_change_notifier_.WatchForAddressChange();

        EXPECT_FALSE(network_change_notifier_.is_watching());
        EXPECT_LT(0, network_change_notifier_.sequential_failures());

        // If a task to notify observers of the IP address change event was
        // incorrectly posted, make sure it gets run.
        base::MessageLoop::current()->RunUntilIdle();
    }

    // Simulates a network change event, resulting in a call to OnObjectSignaled.
    // The resulting call to WatchForAddressChangeInternal then succeeds.
    void SignalAndSucceed()
    {
        EXPECT_TRUE(network_change_notifier_.is_watching());
        EXPECT_EQ(0, network_change_notifier_.sequential_failures());

        EXPECT_CALL(test_ip_address_observer_, OnIPAddressChanged()).Times(1);
        EXPECT_CALL(network_change_notifier_, WatchForAddressChangeInternal())
            .Times(1)
            .WillOnce(Return(true));

        network_change_notifier_.OnObjectSignaled(INVALID_HANDLE_VALUE);

        EXPECT_TRUE(network_change_notifier_.is_watching());
        EXPECT_EQ(0, network_change_notifier_.sequential_failures());

        // Run the task to notify observers of the IP address change event.
        base::MessageLoop::current()->RunUntilIdle();
    }

    // Simulates a network change event, resulting in a call to OnObjectSignaled.
    // The resulting call to WatchForAddressChangeInternal then fails.
    void SignalAndFail()
    {
        EXPECT_TRUE(network_change_notifier_.is_watching());
        EXPECT_EQ(0, network_change_notifier_.sequential_failures());

        EXPECT_CALL(test_ip_address_observer_, OnIPAddressChanged()).Times(1);
        EXPECT_CALL(network_change_notifier_, WatchForAddressChangeInternal())
            // Due to an expected race, it's theoretically possible for more than
            // one call to occur, though unlikely.
            .Times(AtLeast(1))
            .WillRepeatedly(Return(false));

        network_change_notifier_.OnObjectSignaled(INVALID_HANDLE_VALUE);

        EXPECT_FALSE(network_change_notifier_.is_watching());
        EXPECT_LT(0, network_change_notifier_.sequential_failures());

        // Run the task to notify observers of the IP address change event.
        base::MessageLoop::current()->RunUntilIdle();
    }

    // Runs the message loop until WatchForAddressChange is called again, as a
    // result of the already posted task after a WatchForAddressChangeInternal
    // failure.  Simulates a success on the resulting call to
    // WatchForAddressChangeInternal.
    void RetryAndSucceed()
    {
        EXPECT_FALSE(network_change_notifier_.is_watching());
        EXPECT_LT(0, network_change_notifier_.sequential_failures());

        EXPECT_CALL(test_ip_address_observer_, OnIPAddressChanged())
            .Times(1)
            .WillOnce(Invoke(base::MessageLoop::current(),
                &base::MessageLoop::QuitWhenIdle));
        EXPECT_CALL(network_change_notifier_, WatchForAddressChangeInternal())
            .Times(1)
            .WillOnce(Return(true));

        base::MessageLoop::current()->Run();

        EXPECT_TRUE(network_change_notifier_.is_watching());
        EXPECT_EQ(0, network_change_notifier_.sequential_failures());
    }

    // Runs the message loop until WatchForAddressChange is called again, as a
    // result of the already posted task after a WatchForAddressChangeInternal
    // failure.  Simulates a failure on the resulting call to
    // WatchForAddressChangeInternal.
    void RetryAndFail()
    {
        EXPECT_FALSE(network_change_notifier_.is_watching());
        EXPECT_LT(0, network_change_notifier_.sequential_failures());

        int initial_sequential_failures = network_change_notifier_.sequential_failures();

        EXPECT_CALL(test_ip_address_observer_, OnIPAddressChanged()).Times(0);
        EXPECT_CALL(network_change_notifier_, WatchForAddressChangeInternal())
            // Due to an expected race, it's theoretically possible for more than
            // one call to occur, though unlikely.
            .Times(AtLeast(1))
            .WillRepeatedly(Invoke(ExitMessageLoopAndReturnFalse));

        base::MessageLoop::current()->Run();

        EXPECT_FALSE(network_change_notifier_.is_watching());
        EXPECT_LT(initial_sequential_failures,
            network_change_notifier_.sequential_failures());

        // If a task to notify observers of the IP address change event was
        // incorrectly posted, make sure it gets run.
        base::MessageLoop::current()->RunUntilIdle();
    }

private:
    // Note that the order of declaration here is important.

    // Allows creating a new NetworkChangeNotifier.  Must be created before
    // |network_change_notifier_| and destroyed after it to avoid DCHECK failures.
    NetworkChangeNotifier::DisableForTest disable_for_test_;

    StrictMock<TestNetworkChangeNotifierWin> network_change_notifier_;

    // Must be created after |network_change_notifier_|, so it can add itself as
    // an IPAddressObserver.
    StrictMock<TestIPAddressObserver> test_ip_address_observer_;
};

TEST_F(NetworkChangeNotifierWinTest, NetChangeWinBasic)
{
    StartWatchingAndSucceed();
}

TEST_F(NetworkChangeNotifierWinTest, NetChangeWinFailStart)
{
    StartWatchingAndFail();
}

TEST_F(NetworkChangeNotifierWinTest, NetChangeWinFailStartOnce)
{
    StartWatchingAndFail();
    RetryAndSucceed();
}

TEST_F(NetworkChangeNotifierWinTest, NetChangeWinFailStartTwice)
{
    StartWatchingAndFail();
    RetryAndFail();
    RetryAndSucceed();
}

TEST_F(NetworkChangeNotifierWinTest, NetChangeWinSignal)
{
    StartWatchingAndSucceed();
    SignalAndSucceed();
}

TEST_F(NetworkChangeNotifierWinTest, NetChangeWinFailSignalOnce)
{
    StartWatchingAndSucceed();
    SignalAndFail();
    RetryAndSucceed();
}

TEST_F(NetworkChangeNotifierWinTest, NetChangeWinFailSignalTwice)
{
    StartWatchingAndSucceed();
    SignalAndFail();
    RetryAndFail();
    RetryAndSucceed();
}

} // namespace net
